package com.yang.limit.service.impl;

import com.yang.limit.constant.Constant;
import com.yang.limit.constant.RequestLimitType;
import com.yang.limit.dto.RequestLimitDTO;
import com.yang.limit.service.RequestLimitService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.awt.image.Kernel;
import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
public class LeakyBucketServiceImpl implements RequestLimitService {

    private static final DefaultRedisScript<Long> LEAKY_BUCKET_SCRIPT;

    static {
        LEAKY_BUCKET_SCRIPT = new DefaultRedisScript<>();
        LEAKY_BUCKET_SCRIPT.setLocation(new ClassPathResource("leaky_bucket.lua"));
        LEAKY_BUCKET_SCRIPT.setResultType(Long.class);
    }

    @Value("${request-limit.scan-package}")
    private String scanPackage;

    @Autowired
    private ResourcePatternResolver resourcePatternResolver;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Resource(name = "leakyBucketThreadPoolTaskScheduler")
    private ThreadPoolTaskScheduler leakyBucketThreadPoolTaskScheduler;

    @Override
    public RequestLimitType getLimitType() {
        return RequestLimitType.LEAKY_BUCKET;
    }

    @Override
    public boolean checkIsRequestLimit(RequestLimitDTO dto) {
        String key = Constant.REDIS_KEY_LEAKY_BUCKET_PREFIX + dto.getKey();

        List<String> params = new ArrayList<>();
        params.add(key);
        params.add(""+dto.getLimiter().limitCount());
        Long result = redisTemplate.execute(LEAKY_BUCKET_SCRIPT, params);
        if(result == -1){
            log.info("【{}】限流控制，漏桶容量已满，该请求被拦截", dto.getKey());
            return true;
        }
        else{
            log.info("【{}】漏桶中容量未满，当前容量：{}，漏桶容量：{}", dto.getKey(), redisTemplate.opsForValue().get(key), dto.getLimiter().limitCount());
            return false;
        }
    }

    /**
     * 定时任务
     * 减小请求数量，即水滴数量
     */
    @PostConstruct
    public void decreaseRequestNumber(){
        List<RequestLimitDTO> tokenLimitList = this.getTokenLimitList(resourcePatternResolver, RequestLimitType.LEAKY_BUCKET, scanPackage);
        if(tokenLimitList.isEmpty()){
            log.info("项目中没有使用LEAKY-BUCKET限流");
            return;
        }

        tokenLimitList.forEach(requestLimitDTO -> {
            leakyBucketThreadPoolTaskScheduler.scheduleAtFixedRate(() -> {
                String key = Constant.REDIS_KEY_LEAKY_BUCKET_PREFIX + requestLimitDTO.getKey();
                Integer value = (Integer) redisTemplate.opsForValue().get(key);
                if(value!=null){
                    redisTemplate.opsForValue().set(key,Math.max(0,value-requestLimitDTO.getLimiter().limitPeriodCount()));
                }
                else{
                    redisTemplate.opsForValue().set(key,0);
                }
                log.info("当前漏桶【{}】中的请求数{}",requestLimitDTO.getKey(),redisTemplate.opsForValue().get(key));
            },requestLimitDTO.getLimiter().period());
        });
    }
}
